﻿using Autofac;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace AutofacExtension
{
    public static class AutofacBuilderExtensions
    {
        public static void RegistTypes(
             this ContainerBuilder builder, Action<AutofacBuilderOptions> option)
        {
            AutofacBuilderOptions builderOptions = new AutofacBuilderOptions();
            option?.Invoke(builderOptions);

            if (!builderOptions.Assemblies.Any())
            {
                builderOptions.Assemblies.AddRange(AppDomain.CurrentDomain.GetAssemblies().ToList());
            }
            builderOptions.Filters.Add(new RegistTypeFilter
            {
                State = RegistTypeState.Typed,
                Type = typeof(IAutoInject)
            });

            builderOptions.Filters.Add(new RegistTypeFilter
            {
                State = RegistTypeState.Typed,
                Type = typeof(ITypeInject)
            });

            builderOptions.Filters.Add(new RegistTypeFilter
            {
                State = RegistTypeState.Named,
                Type = typeof(INameInject)
            });

            List<Type> types = builderOptions.Assemblies.SelectMany(a => a.GetTypes()).Distinct().ToList();

            foreach (var item in builderOptions.Filters)
            {
                var subs = types.Where(t => t.IsClass && t.GetInterfaces().Contains(item.Type)).ToList();
                RegistTypeResolver.Default.Filters.Add(item.Type, subs);

                foreach (var type in subs)
                {
                    var @inject = type.GetCustomAttribute<InjectAttribute>();
                    if (@inject == null) @inject = new InjectAttribute();
                    if (item.State == RegistTypeState.Named && @inject.Scope == RegistTypeScope.Instance)
                    {
                        builder.RegisterType(type)
                           .Named(type.FullName, item.Type)
                           .AsImplementedInterfaces()
                           .InstancePerDependency()
                           .PropertiesAutowired();

                        builder.RegisterType(type)
                           .AsSelf()
                           .AsImplementedInterfaces()
                           .InstancePerDependency()
                           .PropertiesAutowired();
                    }
                    else if (item.State == RegistTypeState.Named && @inject.Scope == RegistTypeScope.Scope)
                    {
                        builder.RegisterType(type)
                           .Named(type.FullName, item.Type)
                           .AsImplementedInterfaces()
                           .InstancePerLifetimeScope()
                           .PropertiesAutowired();

                        builder.RegisterType(type)
                           .AsSelf()
                           .AsImplementedInterfaces()
                           .InstancePerLifetimeScope()
                           .PropertiesAutowired();
                    }
                    else if (item.State == RegistTypeState.Named && @inject.Scope == RegistTypeScope.Singleton)
                    {
                        builder.RegisterType(type)
                           .Named(type.FullName, item.Type)
                           .AsImplementedInterfaces()
                           .SingleInstance()
                           .PropertiesAutowired();

                        builder.RegisterType(type)
                           .AsSelf()
                           .AsImplementedInterfaces()
                           .SingleInstance()
                           .PropertiesAutowired();
                    }
                    else if (item.State == RegistTypeState.Typed)
                    {
                        if (!type.IsGenericType && !type.ContainsGenericParameters && !type.GetGenericArguments().Any())
                        {
                            var interfaces = type.GetInterfaces().Where(f => f != typeof(IDisposable) && f != item.Type);
                            if (!interfaces.Any() && @inject.Scope == RegistTypeScope.Instance)
                            {
                                builder.RegisterType(type)
                                            .AsSelf()
                                            .AsImplementedInterfaces()
                                            .InstancePerDependency()
                                            .PropertiesAutowired();
                            }
                            if (!interfaces.Any() && @inject.Scope == RegistTypeScope.Scope)
                            {
                                builder.RegisterType(type)
                                            .AsSelf()
                                            .AsImplementedInterfaces()
                                            .InstancePerLifetimeScope()
                                            .PropertiesAutowired();
                            }
                            if (!interfaces.Any() && @inject.Scope == RegistTypeScope.Singleton)
                            {
                                builder.RegisterType(type)
                                            .AsSelf()
                                            .AsImplementedInterfaces()
                                            .SingleInstance()
                                            .PropertiesAutowired();
                            }

                            foreach (var @interface in interfaces)
                            {
                                if (@inject.Scope == RegistTypeScope.Instance)
                                {
                                    builder.RegisterType(type)
                                   .As(@interface)
                                   .AsImplementedInterfaces()
                                   .InstancePerDependency()
                                   .PropertiesAutowired();
                                }
                                else if (@inject.Scope == RegistTypeScope.Scope)
                                {
                                    builder.RegisterType(type)
                                   .As(@interface)
                                   .AsImplementedInterfaces()
                                   .InstancePerLifetimeScope()
                                   .PropertiesAutowired();
                                }
                                else if (@inject.Scope == RegistTypeScope.Singleton)
                                {
                                    builder.RegisterType(type)
                                   .As(@interface)
                                   .AsImplementedInterfaces()
                                   .SingleInstance()
                                   .PropertiesAutowired();
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    public class AutofacBuilderOptions
    {
        public AutofacBuilderOptions()
        {
            Filters = new List<RegistTypeFilter>();
            Assemblies = new List<Assembly>();
        }
        public List<Assembly> Assemblies { get; }
        public List<RegistTypeFilter> Filters { get; }
    }

    public class RegistTypeFilter
    {
        public Type Type { get; set; }
        public RegistTypeState State { get; set; }
    }

    public enum RegistTypeScope
    {
        Scope = 0,
        Instance = 1,
        Singleton = 2
    }

    public enum RegistTypeState
    {
        Named = 0,
        Typed = 1
    }
}
